// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.android.apps.moderator;

import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A prompt for the user to log in to their Google account and return an auth token.
 */
public class LoginActivity extends ModeratorActivity {

  /**
   * A dialog that prompts the user to enter a captcha, when the login service
   * prompts the app for one. 
   */
  class CaptchaDialog extends Dialog {

    public CaptchaDialog(String uriString) {
      super(LoginActivity.this);
      setContentView(R.layout.captcha);
      setTitle(getResources().getString(R.string.captcha_challenge));
      ImageView imageView = (ImageView) findViewById(R.id.CaptchaImage);

      try {
        imageView.setImageBitmap(BitmapFactory.decodeStream(new URL(uriString).openConnection()
            .getInputStream()));
      } catch (MalformedURLException e) {
        // Error getting captcha image? Log the error and close the dialog
        // User will be returned to the original form
        e.printStackTrace();
        dismiss();
      } catch (IOException e) {
        // Error getting captcha image? Log the error and close the dialog
        // User will be returned to the original form
        e.printStackTrace();
        dismiss();
      }
    }

    public String getGuess() {
      EditText capchaGuess = (EditText) findViewById(R.id.CaptchaGuess);
      return capchaGuess.getText().toString();
    }

    public void setOnSignInListener(final View.OnClickListener onClickListener) {
      Button submitButton = (Button) findViewById(R.id.CaptchaSubmitButton);
      submitButton.setOnClickListener(new View.OnClickListener(){
        public void onClick(View view) {
          onClickListener.onClick(view);
          dismiss();
        }
      });
    }
  }

  // Keys for storing login details in preferences
  private static final String AUTH_KEY = "com.google.android.apps.moderator.Auth";
  // Authtoken global to application. Presense of a non-empty authtoken means
  // the user is logged in
  private static String authToken = "";

  private static final String USER_NAME_KEY = "com.google.android.apps.moderator.UserName";

  /**
   * Will supply the auth token to any part of the app that requires it
   */
  static String getAuthToken(Context context) {
    // Token blank? Attempt to recover from preferences
    if ("".equals(authToken)) {
      final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
      authToken = preferences.getString(AUTH_KEY, "");
    }
    return authToken;
  }

  /**
   * Utility application to recover key/value pairs from http replies
   */
  private static Map<String, String> getKeyValues(InputStream stream) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
    Map<String, String> kvp = new HashMap<String, String>();
    while (true) {
      String line = reader.readLine();
      if (line == null) {
        return kvp;
      }
      String[] params = line.split("=", 2);
      kvp.put(params[0], params[1]);
    }
  }

  /**
   * Signs the user out by deleting the authToken
   */
  public static void logOut(Context context) {
    authToken = "";
    rememberAuthToken(context);
  }
  
  /**
   * Stores the auth token in the secure preferences associated with the app
   */
  private static void rememberAuthToken(Context context) {
    final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    Editor edit = preferences.edit();
    edit.putString(AUTH_KEY, authToken);
    edit.commit();
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // The only thing that can generate results is the AccountManager authenticator intent,
    // called via the wrapper. If result is OK, user has granted permission and we should attempt
    // again to get the result.
    if (resultCode == RESULT_OK) {
      AccountManagerWrapper.tryGetAuthToken(this);
    }
  }
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.login);
    setHasContent(true); // Stops errors exiting the activity
    final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

    // An autocomplete view is used to reduce user typing - we suggest '@gmail.com' as the user
    // types their address.
    final AutoCompleteTextView editUserName =
        (AutoCompleteTextView) findViewById(R.id.EditUserName);
    editUserName.addTextChangedListener(new TextWatcher() {
      public void afterTextChanged(Editable s) {
      }
      public void beforeTextChanged(CharSequence s, int start, int count, int after) {
      }
      public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (s.toString().contains("@")) {
          editUserName.setAdapter(
              new ArrayAdapter<String>(LoginActivity.this, R.layout.autocomplete, new String[]{}));
        } else {
          // If the currently-type email address doesn't include @, make a suggestion
          editUserName.setAdapter(new ArrayAdapter<String>(LoginActivity.this,
              R.layout.autocomplete, new String[]{s.toString() + "@gmail.com"}));
        }
      }
    });

    // 2.* phones can use the AccountManager to get the email address of the user.
    try {
      String name = AccountManagerWrapper.getEmail(this);
      if (name != null) {
        editUserName.setText(name);
        findViewById(R.id.EditPassword).requestFocus();
      }
    } catch (VerifyError e) {
      // Normal and expected if the AccountManager is not available (e.g. 1.*)
    }

    // No user name successfully set? Attempt to get it from the preferences
    if ("".equals(editUserName.getText().toString())) {
      editUserName.setText(preferences.getString(USER_NAME_KEY, ""));
    }

    // Set up the Sign In button
    final Button signInButton = (Button) findViewById(R.id.SignInButton);
    signInButton.setOnClickListener(new OnClickListener() {
      public void onClick(View v) {
        showProgress();
        // Signing in happens on a separate thread because there are delays
        new Thread(new Runnable() {
          public void run() {
            tryLogin(null, null);
          }
        }).start();
      }
    });

    super.onCreate(savedInstanceState);
    
    try {
      // An attempt is made to use the built-in authenticator at this point.
      // If it fails for any reason this activity (which prompts the user) will run instead.
      AccountManagerWrapper.tryGetAuthToken(this);
    } catch (VerifyError e) {
      // Happens on non 2.* devices
      
    }
  }
    
  /**
   * Set the authToken and close the activity
   */
  public void setAuthToken(String value) {
    authToken = value;
    // User has elected to stay signed in permanently?
    CheckBox checkbox = (CheckBox) findViewById(R.id.SignInCheckbox);
    if (checkbox.isChecked()) {
      rememberAuthToken(LoginActivity.this);
    }
    // Tell the user they are logged in
    runOnUiThread(new Runnable() {
      public void run() {
        Toast.makeText(LoginActivity.this,
            getResources().getString(R.string.signed_in), TOAST_DURATION).show();
      }
    });
    // End the activity
    finish();
  }

  /**
   * Attempt to login with the supplied credentials
   * @param loginToken the loginToken from a captcha challenge (or null)
   * @param captchaGuess the guess from a captcha challenge (or null)
   */
  private void tryLogin(String loginToken, String captchaGuess) {
    AutoCompleteTextView editUserName = (AutoCompleteTextView) findViewById(R.id.EditUserName);
    EditText editPassword = (EditText) findViewById(R.id.EditPassword);
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

    String userNameString = editUserName.getText().toString();
    String passwordString = editPassword.getText().toString();

    Editor edit = preferences.edit();
    edit.putString(USER_NAME_KEY, userNameString);
    edit.commit();

    DefaultHttpClient client = new DefaultHttpClient();
    try {
      // Add the credentials to the request
      final List<NameValuePair> values = new ArrayList<NameValuePair>();
      values.add(new BasicNameValuePair("accountType", "GOOGLE"));
      values.add(new BasicNameValuePair("Email", userNameString));
      values.add(new BasicNameValuePair("Passwd", passwordString));
      values.add(new BasicNameValuePair("service", "moderator"));
      values.add(new BasicNameValuePair("source", "Google-ModeratorAndroid-1"));
      if (loginToken != null) {
        values.add(new BasicNameValuePair("logintoken", loginToken));
        values.add(new BasicNameValuePair("logincaptcha", captchaGuess));
      }

      HttpPost post = new HttpPost(new URI("https://www.google.com/accounts/ClientLogin"));
      post.setEntity(new UrlEncodedFormEntity(values, HTTP.UTF_8));
      HttpResponse response = client.execute(post);
      HttpEntity entity = response.getEntity();
      final Map<String, String> kvp = getKeyValues(entity.getContent());
      hideProgress();
      if (kvp.containsKey("Auth")) {
        // We have an auth key
        setAuthToken(kvp.get("Auth"));
      } else if (kvp.containsKey("CaptchaUrl")) {
        // Sign in API requires a captcha challenge. Use a modal dialog to show the image
        // to the user and retry with their entry.
        runOnUiThread(new Runnable() {

          public void run() {
            String uriString = "https://www.google.com/accounts/" + kvp.get("CaptchaUrl");
            final CaptchaDialog captchaDialog = new CaptchaDialog(uriString);
            captchaDialog.setOnSignInListener(new View.OnClickListener(){
                public void onClick(View view) {
                  // Get off UI thread and re-try
                  new Thread(new Runnable() {
                    public void run() {
                      tryLogin(kvp.get("CaptchaToken"), captchaDialog.getGuess());
                    }
                  }).start();
                }
              });
            captchaDialog.show();
          }
        });
      } else {
        // Failure (no auth token). Report the general error to the user.
        alert(R.string.cannot_sign_in);
      }

      entity.consumeContent();
    } catch (URISyntaxException e) {
      generalError(e, R.string.a_problem_occurred);
    } catch (UnsupportedEncodingException e) {
      generalError(e, R.string.a_problem_occurred);
    } catch (ClientProtocolException e) {
      generalError(e, R.string.a_problem_occurred);
    } catch (IOException e) {
      generalError(e, R.string.connection_problem);
    } finally {
      client.getConnectionManager().closeExpiredConnections();
    }
  }

}
